package il.technion.cs236369.webserver;

import il.technion.cs236369.common.Common;
import il.technion.cs236369.exceptions.InvalidRequestMethod;
import il.technion.cs236369.exceptions.InvalidRequestParams;
import il.technion.cs236369.exceptions.NullEntityInPost;
import il.technion.cs236369.threadPool.ThreadPool;

import java.io.IOException;
import java.net.Socket;

import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.BasicHttpParams;

/**
 * Socket Reader Class. Handles a single socket incoming request and gets the
 * HTTP request.
 * 
 * @author gil
 * 
 */
public class SocketReader implements Runnable {
	private final ThreadPool	_requestHandlerThreadPool;
	private final Socket		_socket;

	/**
	 * Creates a new Socket Reader.
	 * 
	 * @param configuration The webserver's configuration.
	 * @param socket The incoming socket.
	 * @param requestHandlerThreadPool The thread pool to insert the created
	 *            request to.
	 */
	public SocketReader(Socket socket, ThreadPool requestHandlerThreadPool) {
		_requestHandlerThreadPool = requestHandlerThreadPool;
		_socket = socket;
	}

	@Override
	public void run() {
		DefaultHttpServerConnection inconn = new DefaultHttpServerConnection();
		Common.getOut().println("Incoming connection from " + _socket.getInetAddress());
		try {
			inconn.bind(_socket, new BasicHttpParams());
		} catch (IOException e1) {
			System.err.println("Unable to bind incoming socket");
			try {
				_socket.close();
			} catch (IOException e) {
				System.err.println("Unable to close incoming socket");
			}
			return;
		}
		//get request
		Request request = null;
		try {
			HttpRequest hrequest = inconn.receiveRequestHeader();
			HttpEntity entity = null;
			if (hrequest instanceof HttpEntityEnclosingRequest) {
				inconn.receiveRequestEntity((HttpEntityEnclosingRequest)hrequest);
				entity = ((HttpEntityEnclosingRequest)hrequest).getEntity();
			}
			//create the request object
			request = new Request(_socket, inconn, hrequest, entity, WebServerConfiguration.getInstance().getBaseDirName());
		} catch (InvalidRequestParams e) {
			System.err.println("InvalidRequestParams: " + e.getMessage());
			handleRequestException(_socket, inconn, "Can't process request. Invalid request parameters...");
			return;
		} catch (InvalidRequestMethod e) {
			System.err.println("InvalidRequestMethod: " + e.getMessage());
			HttpResponse response = RequestHandler.generateErrorResponse(405, "Method Not Allowed", "Unsupported HTTP request method");
			response.setHeader("Allow", "GET, POST");
			RequestHandler.sendResponseAndClose(_socket, inconn, response);
			return;
		} catch (HttpException | IOException e) {
			System.err.println("Unable to get request");
			handleRequestException(_socket, inconn, "Unable to get request...");
			return;
		} catch (NullEntityInPost e) {
			System.err.println("NullEntityInPost: Found no entity in a POST request");
			handleRequestException(_socket, inconn, "Found no entity in a POST request");
			return;
		}
		assert (request != null);

		//create the request handler and add to pool
		RequestHandler requestHandler = new RequestHandler(request);
		_requestHandlerThreadPool.runOrWait(requestHandler, new TimeoutHandler(_socket)); //blocking (if queue is full)
	}

	private void handleRequestException(Socket socket, DefaultHttpServerConnection conn, String description) {
		//got an exception while receiving the request
		HttpResponse response = RequestHandler.generateErrorResponse(400, "Bad Request", description);
		RequestHandler.sendResponseAndClose(socket, conn, response);
	}

}
